/*
 * File:     wiiuseconnector.cpp
 * Authors:  Dominic Stalder, Thomas Bruederli
 *
 * Thread class to connect to bluetooth Wii devices using the wiiuse library.
 *
 * This file is part of the Qt-based cross-platform Balance Blocks Game
 *
 * Copyright (C) 2009-2010, Dominic Stalder & Thomas Bruederli
 * Licensed under the GNU Lesser General Public License (LGPL) as published
 * by the Free Software Foundation at http://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * $Id: wiiuseconnector.cpp 3 2010-02-17 18:17:34Z roundcube $
 */


#include "wiiuseconnector.h"
#include "wiidevicedriver.h"
#include <iostream>

// initialize static member
WiiuseConnector* WiiuseConnector::instance = NULL;


/**
 * Singleton
 */
WiiuseConnector* WiiuseConnector::getInstance()
{
	if (!instance)
		instance = new WiiuseConnector;
	return instance;
}

/**
 * Constructor
 */
WiiuseConnector::WiiuseConnector() : wiimotes(NULL), wiimoterequest(NULL), running(0), connected(0), clients(0)
{
	reset();
}

/**
 * Destructor
 */
WiiuseConnector::~WiiuseConnector()
{
	// locks the mutex, sets the run variable to stop and unlocks the mutex
	mutex.lock();
	running = 0;
	mutex.unlock();

	// waits for the thread to be killed
	wait();
}

/**
 * A client requests to be connected with a wiimore
 * and registers itself as callback obejct.
 *
 * @param device The WiiDeviceDriver instance
 * @return True if request has been accepted, false if connector is busy and rejected the request
 */
bool WiiuseConnector::requestWiimote(WiiDeviceDriver *device)
{
	if (!wiimoterequest)
	{
		wiimoterequest = device;
		clients++;
		return true;
	}

	return false;
}

/**
 * Disconnect a WiiDeviceDriver object from the wiiuse thread.
 *
 * @param device The WiiDeviceDriver instance to disconnect
 */
void WiiuseConnector::disconnect(WiiDeviceDriver *device)
{
	mutex.lock();

	// device is currently waiting for connections
	if (wiimoterequest && wiimoterequest == device)
	{
		wiimoterequest = NULL;
	}
	else
	{
		// find device in drivers list and remove it
		for (int i=0; i<MAX_WIIMOTES; i++)
			if (drivers[i] == device)
				drivers[i] = NULL;
	}

	// decrease number of cilents
	if (clients > 0)
		clients--;

	// stop thread if there are no more active clients
	if (!clients)
		running = 0;

	mutex.unlock();
}

/**
 * QThread run-loop
 */
void WiiuseConnector::run()
{
	// locks the mutex, sets the run variable to start and unlocks the mutex
	mutex.lock();
	running = 1;
	mutex.unlock();

	// initialize datastructures
	wiimotes = wiiuse_init(MAX_WIIMOTES);

	if (wiimotes != NULL)
		state = WiiDeviceDriver::STATE_NOTCONNECTED;

	// runs throw the different wii connection states
	while(running)
	{
		switch(state)
		{
		case WiiDeviceDriver::STATE_NOTCONNECTED:
			connect();
			break;
		case WiiDeviceDriver::STATE_CONNECTED:
			poll();
			break;
		default:
			stop();
			break;
		}
	}

	// disconnect wiimotes
	std::cout << "Disconnecting devices..." << std::endl;
	wiiuse_cleanup(wiimotes, MAX_WIIMOTES);

	// reset state
	reset();
}

/**
 * Stop the runloop when the last client disconnects
 */
void WiiuseConnector::stop()
{
	mutex.lock();
	running = 0;
	mutex.unlock();
}

/**
 * Override <code>QThread::wait</code>
 *
 * Returns true immediately if there are other clients connected and the thred is still running
 */
bool WiiuseConnector::wait()
{
	if (clients <= 0)
		return QThread::wait();
	else
		return true;
}

/**
 * Connect to bluetooth drivers.
 *
 * triggers wiiuse_find() and wiiuse_connect()
 */
void WiiuseConnector::connect()
{
	if (!connected)
	{
		// sets the bluetooth stack of the wiiuse driver to Windows XP and Widcomm stack
		wiiuse_set_bluetooth_stack(wiimotes, MAX_WIIMOTES, WIIUSE_STACK_MS);

		// discovers two wii drivers for a maximum duration of 5 seconds. Returns the
		// number of wii drivers found.
		int found = wiiuse_find(wiimotes, MAX_WIIMOTES, 5);

		// tests if any device was found
		if (!found)
		{
			// no device found. Restart discovering...
			usleep(100000);
		}
		else
		{
			connected = wiiuse_connect(wiimotes, MAX_WIIMOTES);
			// DEBUG:
			std::cout << "Connected to " << connected << " devices" << std::endl;
		}
	}

	if (connected)
		state = WiiDeviceDriver::STATE_CONNECTED;
}

/**
 * Poll wiiuse drivers and delegate events to registered drivers
 */
void WiiuseConnector::poll()
{
	if (wiiuse_poll(wiimotes, MAX_WIIMOTES))
	{
		for (int i=0; i < connected; ++i)
		{
			// connection request pending and this wiimote is not yet connected
			if (wiimoterequest && !drivers[i])
			{
				// DEBUG:
				std::cout << "Checking device " << i << " has type " << wiimotes[i]->exp.type << std::endl;

				if ((wiimoterequest->deviceType == WiiDeviceDriver::TYPE_BALANCEBOARD) == (wiimotes[i]->exp.type == EXP_WII_BOARD))
				{
					wiimoterequest->wiimoteConnect(wiimotes[i]);
					drivers[i] = wiimoterequest;
					wiimoterequest = NULL;
				}
			}
			// delegate event to connected WiiDeviceDriver instance
			else if (drivers[i])
				drivers[i]->handleEvent(wiimotes[i]->event);
		}
	}
}

/**
 * Reset all member variables
 */
void WiiuseConnector::reset()
{
	// initialize all pointers to zero
	for (int i=0; i<MAX_WIIMOTES; i++)
		drivers[i] = NULL;

	state = WiiDeviceDriver::STATE_NONE;
	connected = clients = 0;
	wiimoterequest = NULL;
}
